feat: support BIP-322 P2WSH multisig signatures in auth#3637
feat: support BIP-322 P2WSH multisig signatures in auth#3637TaprootFreak wants to merge 7 commits into
Conversation
Adds a pure verifier mirroring the backend implementation in DFXswiss/api#3637, exposed both as a standalone function (verifyBip322Signature) and as MultisigHDWallet.verifyMessage so the existing Sign/Verify Message screen works symmetrically: a signature produced by signMessage on one device can be verified on any device holding the same wallet (or just the address). Verifier algorithm: - strict P2WSH address detection (bc1q…, 62 chars, 32-byte program) - base64 → witness stack (varint-prefixed items) - last item must be the witness script; sha256 must match the bech32 program - script must be standard OP_M … OP_N OP_CHECKMULTISIG with 33-byte pubkeys - exactly M signatures + empty CHECKMULTISIG dummy - BIP-143 sighash over the synthetic to_spend / to_sign pair - ECDSA verify each sig against pubkeys in script order, sighash flag 0x01 Adds 7 tests covering: round-trip with signMessage, multiple message lengths (incl. unicode and 200-char), wrong-message rejection, wrong-address rejection, PSBT cross-device round-trip verification, malformed-input handling, and isP2wshAddress detection.
There was a problem hiding this comment.
Can't we use the @dfx.swiss/bip322-multisig package here?
Replace ~140 lines of manual BIP-143 sighash construction and BIP-322 tagged-hash computation with primitives from @dfx.swiss/bip322-multisig (bip322MessageHash, buildToSpendTx, p2wshScriptPubKey) and bitcoinjs-lib Transaction.hashForWitnessV0. Eliminates: hashMessage, computeToSpendTxid, computeBip143Sighash, hash256, uint32LE, uint64LE, encodeVarInt, encodeVarBytes. Keeps: witness decoding, multisig script parsing, ECDSA verification. Test helpers also simplified using buildSortedMultisigScript and p2wshAddress from the package.
The 'verifySignature with offchainAddr' describe block was missing a fetch mock unlike the other two blocks. This caused getServerInfo() to make real HTTP calls to arkade.computer and delegate.arkade.money, timing out in CI after 5 seconds.
|
Done — refactored to use |
|
|
Verification is being moved into the Once that's merged and published ( import { isP2wshAddress, verifyBip322P2wshSignature } from '@dfx.swiss/bip322-multisig';And |
Import isP2wshAddress and verifyBip322P2wshSignature directly from the package instead of a local util file. Deletes bip322-p2wsh.util.ts and its spec.
|
Found this PR while looking into options for integrating DFX into our app (with two use cases in our mind currently: BTC buy into Arkade, BTC buy into Taproot or Multisig wallet). I did create |
Adds a pure verifier mirroring the backend implementation in DFXswiss/api#3637, exposed both as a standalone function (verifyBip322Signature) and as MultisigHDWallet.verifyMessage so the existing Sign/Verify Message screen works symmetrically: a signature produced by signMessage on one device can be verified on any device holding the same wallet (or just the address). Verifier algorithm: - strict P2WSH address detection (bc1q…, 62 chars, 32-byte program) - base64 → witness stack (varint-prefixed items) - last item must be the witness script; sha256 must match the bech32 program - script must be standard OP_M … OP_N OP_CHECKMULTISIG with 33-byte pubkeys - exactly M signatures + empty CHECKMULTISIG dummy - BIP-143 sighash over the synthetic to_spend / to_sign pair - ECDSA verify each sig against pubkeys in script order, sighash flag 0x01 Adds 7 tests covering: round-trip with signMessage, multiple message lengths (incl. unicode and 200-char), wrong-message rejection, wrong-address rejection, PSBT cross-device round-trip verification, malformed-input handling, and isP2wshAddress detection.
Summary
bc1q…62 chars) in the auth flow, so users can sign in withwsh(sortedmulti(t, …))/wsh(multi(t, …))wallets.crypto.service.ts:verifyBitcoinBased, only when no message-prefix is set (Bitcoin mainnet) and the address passes strict P2WSH detection — existing P2WPKH/P2SH-P2WPKH/P2TR/legacy paths are untouched.OP_M … OP_N OP_CHECKMULTISIGwitness scripts with SIGHASH_ALL signatures. Miniscript / Taproot scripts / non-standard policies are rejected.Implementation notes
@dfx.swiss/bip322-multisigprimitives (bip322MessageHash,buildToSpendTx,p2wshScriptPubKey) for BIP-322 tagged hash and to_spend transaction construction.bitcoinjs-libTransaction.hashForWitnessV0for BIP-143 sighash computation — no manual byte-level sighash assembly.@noble/curves(already in deps).typeof address !== 'string'guards on the public entry points, matching the precedent inbech32m.service.ts.Client-side caveat
Sparrow Wallet's Sign/Verify Message dialog does not accept multisig wallets (hard-coded
IllegalArgumentException, issue #193). Producing a BIP-322 simple signature for a multisig today requires either Bitcoin Knotssignmessage, or building the BIP-322to_signPSBT externally and signing it through Sparrow's regular multisig PSBT flow. The backend code here is signing-tool-agnostic.Test plan
bip322-p2wsh.util.spec.ts— 15 tests:it.todoplaceholder for a real wallet-produced fixturecrypto.service.spec.ts— 67 existing tests still green/auth